package core;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

public class GraphBalancer extends Thread {
	Graph g;
	
	static int ScanInterval = 1000;
	
	private volatile boolean done = false;

	HashMap<Entity, ArrayList<Integer>> TokenNumTable = new HashMap<Entity, ArrayList<Integer>>();
	ArrayList<Process> procPoolInc;
	ArrayList<Process> procPoolDec;

	public GraphBalancer(Graph g,int si) {
		this.g = g;
		
		ScanInterval = si;
		
		for (Entity e : g.entities) {
			ArrayList<Integer> record = new ArrayList<Integer>();
			this.TokenNumTable.put(e, record);
		}
		
		
	}

	public void run() {
		EntityScanner es = new EntityScanner(this);
		es.start();

		System.out.println("**Graph Balancer Started");

		try {
			sleep(ScanInterval*3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		while (!done) {

			System.out.println("**"
					+ new SimpleDateFormat("[yyyy-MM-dd] HH:mm:ss")
							.format(new Date()));

			procPoolInc = new ArrayList<Process>();
			procPoolDec = new ArrayList<Process>();

			// 累计未处理token最多的Process
			Process thisProc = null;
			int maxCount = 0;

			// 累计未处理token最少的Process
			Process thatProc = null;
			int minCount = Integer.MAX_VALUE;

			for (Process p : g.processes.keySet()) {
				boolean b1 = true;
				boolean b2 = true;

				// 联入的Entities中token最小的数量
				int minSize = Integer.MAX_VALUE;

				for (Entity e : p.entityIn) {

					int[] diff = new int[2];
					int recordLength = TokenNumTable.get(e).size();

					if (TokenNumTable.get(e).get(recordLength - 1) < minSize) {
						minSize = TokenNumTable.get(e).get(recordLength - 1);
					}

					if (recordLength >= 3) {
						diff[1] = TokenNumTable.get(e).get(recordLength - 1)
								- TokenNumTable.get(e).get(recordLength - 2);
						diff[0] = TokenNumTable.get(e).get(recordLength - 2)
								- TokenNumTable.get(e).get(recordLength - 3);
					}
					if (diff[0] > 0 && diff[1] > 0/* && diff[1]>diff[2] */) {
						// 
						b1 = b1 && true;
					} else
						b1 = b1 && false;
					if (diff[0] < 0 && diff[1] < 0) {
						b2 = b2 && true;
					} else
						b2 = b2 && false;
				}

				if (b1)
					procPoolInc.add(p);
				if (b2)
					procPoolDec.add(p);

				if (minSize > maxCount) {
					maxCount = minSize;
					thisProc = p;
				}

				if (minSize < minCount) {
					minCount = minSize;
					thatProc = p;
				}
			}

			if (thisProc != null) {
				if (procPoolDec.contains(thisProc)) {
					procPoolDec.remove(thisProc);
				}
				procPoolInc.add(thisProc);
			}

			if (thatProc != null) {
				if (!procPoolInc.contains(thatProc)) {
					procPoolDec.add(thatProc);
				}
			}

			for (Process p : procPoolInc) {
				if (p.implTable.size() <= 20) {
					Node node = Node.getFreeNode();
					if (node != null) {
						// 在这个节点加载这个Process需要的Entity
						for (Entity e : p.entityIn) {
							if (!e.nodesImplTo.contains(node)) {
								e.nodesImplTo.add(node);
								e.implTo(node);
							}
						}
						for (Entity e : p.entityOut) {
							if (!e.nodesImplTo.contains(node)) {
								e.nodesImplTo.add(node);
								e.implTo(node);
							}
						}

						// 在这个节点上部署Process
						p.implTo(node);
						System.out.println("**\tAdd Impl: [Process:" + p.name
								+ "] -> [Node:" + node.ip + "]");
					}
				}
			}

			for (Process p : procPoolDec) {
				try {
					if (p.implTable.size() > 1) {
						System.out.println("**\tRemove Impl: [Process:"
								+ p.name + "] -> [Node:" + p.removeAImpl().ip
								+ "]");
					}
				} catch (Exception ex) {
					System.out.println(p.name);
					ex.printStackTrace();
				}
			}

			try {
				sleep(ScanInterval*3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		es.setDone();
		this.printResultToFile();
		System.out.println("**Graph Balancer Quit");
	}

	public void setDone() {
		done = true;
	}

	public void printResultToFile() {

		File log = new File("balancer-output");
		if (!log.exists())
			log.mkdir();
		String fileName = new SimpleDateFormat("[yyyy-MM-dd] HH-mm-ss")
				.format(new Date());
		try {
			FileOutputStream out = new FileOutputStream("balancer-output/"
					+ fileName + ".txt");
			PrintStream ps = new PrintStream(out);
			for (Entity e : TokenNumTable.keySet()) {
				String line = e.name + "={";
				for (Integer i : TokenNumTable.get(e)) {
					line += i + ",";
				}
				line = line.substring(0, line.length() - 1);
				line += "};";
				ps.println(line);
			}
			ps.close();
			out.close();
			System.out.println("**Outputs saved as balancer-output/");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

class EntityScanner extends Thread {
	GraphBalancer gb;
	private volatile boolean done = false;

	EntityScanner(GraphBalancer gb) {
		this.gb = gb;
	}

	public void run() {
		System.out.println("**Entity Scanner Started");

		while (!done) {
			for (Entity e : gb.TokenNumTable.keySet()) {
				gb.TokenNumTable.get(e).add(e.getRemoteTokenNum());
			}
			try {
				sleep(GraphBalancer.ScanInterval);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}

		System.out.println("**Entity Scanner Quit");
	}

	public void setDone() {
		done = true;
	}
}